home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / COMM / XFER10 / SXRX.INC next >
Text File  |  1990-01-17  |  48KB  |  977 lines

  1. { The code in this file handles transmit and receive of Xmodem Checksum, }
  2. { CRC, and 1K.  This is the first include file of XFER.PAS.              }
  3.  
  4. { Copyright (C) 1990 By Andrew Bartels }
  5. { A Product of Digital Innovations     }
  6.  
  7.  
  8. { This function transmits via Xmodem protocol the file contained in        }
  9. { FileName.  If CRCMode is True, a CRC check is used on all blocks, else   }
  10. { a checksum is used.  If OneK is True, each data block is sent 1K long,   }
  11. { else each data block is 128 bytes long.  All errors and transfer results }
  12. { are returned through the function.  These codes are defined in the       }
  13. { opening Const definition at the beginning of XFER.PAS.                   }
  14.  
  15. Function XmodemSend(     CRCMode,
  16.                          OneK      : Boolean;
  17.                      Var FileName  : String)   : Byte;
  18.  
  19. Var Ch            : Char;     { Character just received                    }
  20.     TimeOut       : Boolean;  { Becomes True if a Timeout condition occurs }
  21.  
  22.     NumNAKS       : Integer;  { Hold number of NAKs receiver has sent on   }
  23.                               { current block.  If this value exceeds 10,  }
  24.                               { then receiver cancels protocol by sending  }
  25.                               { two CAN's                                  }
  26.  
  27.     BlockNum,                 { Contains the current block being xmitted.  }
  28.                               { Starts at 1, and increments by one until   }
  29.                               { it becomes 255, at which point, it rolls   }
  30.                               { over to 0, and continues incrementing.     }
  31.  
  32.     TotalErrors   : Byte;     { Stores total number of NAK's and other     }
  33.                               { error conditions that have occurred thus   }
  34.                               { far in the protocol, pending or not.       }
  35.  
  36.     TotalBlocks,              { Contains the total number of blocks sent   }
  37.                               { thus far.  Similar to BlockNum, except this}
  38.                               { one does not roll over at 255.             }
  39.  
  40.     BytesSent : LongInt;      { Keeps count of how many bytes of file data }
  41.                               { have been successfully sent thus far.      }
  42.  
  43.     Hour,                     { Holds Hour from GetTime call               }
  44.     Min,                      { Holds Minute from GetTime call             }
  45.     Sec,                      { Holds Second from GetTime call             }
  46.     Hund          : Word;     { Holds Hundredths from GetTime call         }
  47.  
  48.     StartTime,                { Holds time in seconds of when protocol was }
  49.                               { initiated.  Used in determining Chars/Sec  }
  50.  
  51.     EndTime       : Real;     { Holds the time in seconds as when last blk }
  52.                               { was transmitted.  Used with StartTime in   }
  53.                               { figuring Char/Sec.                         }
  54.  
  55.     Data          : BlockType;{ This holds the data block to be xmitted.   }
  56.  
  57.     DataLen       : Word;     { Is 128 for a 128 byte block, or 1024 for a }
  58.                               { 1K block.                                  }
  59.  
  60.     CancelCode    : Byte;     { Contains the number of CAN's that came from}
  61.                               { receiver in a row.  If this is ever 2, then}
  62.                               { protocol aborts.                           }
  63.  
  64.     File1         : File;     { File handle used for disk I/O              }
  65.  
  66.  
  67.  
  68.     { This sub-procedure is the main instrument of disk I/O for xmit.      }
  69.     { In 1K transfers, data blocks returned from this procedure are 1K     }
  70.     { long until there is less than 1K of the file left to be sent, at     }
  71.     { which point 128 byte blocks are returned.  In regular xfers, 128 byte}
  72.     { data blocks are always returned.                                     }
  73.  
  74.     Procedure GetNextBlock (Var Data    : BlockType;
  75.                             Var DataLen : Word      );
  76.     Begin
  77.       FillChar(Data,1024,#26);      { all of block is set to ASCII code 26 }
  78.                                     { for CP/M systems that require #26 at }
  79.                                     { end of file.  These will be written  }
  80.                                     { over a little later if this is not   }
  81.                                     { the last block we're sending.        }
  82.  
  83.       If (FileSize(File1) - FilePos(File1) >= 1024) and OneK
  84.         then   {If it's OK to send a 1K block, then read in 1K of data }
  85.           Begin
  86.             BlockRead(File1,Data,1024,DataLen);
  87.           End
  88.         else
  89.           Begin  { Otherwise, read in 128 bytes of data }
  90.             BlockRead(File1,Data,128,DataLen);
  91.             If (DataLen < 128) and (DataLen <> 0)  { If we couldn't get    }
  92.               then                        { 128 bytes due to EOF, then we  }
  93.                 DataLen := 128;           { say block is 128 bytes anyway, }
  94.                                           { knowing there are ASCII 26's   }
  95.                                           { on the end of the block to fill}
  96.                                           { up the rest of it.             }
  97.  
  98.           End;
  99.     End;
  100.  
  101.  
  102. Begin  { OK, here's where we start the xfer }
  103.  
  104.   If not Com_Carrier     { If there is no carrier, then exit because we     }
  105.     then                 { cannot send data.  Correct error code is returned}
  106.       Begin
  107.         XmodemSend := CarrierLostError;
  108.         Exit;
  109.       End;
  110.  
  111.   XmodemSend := NoError;                { Assume no error has occurred yet.}
  112.  
  113.   If not OpenFile(File1,Rd,1,FileName)  { Try opening the file to read     }
  114.     then
  115.       Begin
  116.         XmodemSend := FileError;        { If we can't open the file, error }
  117.         Exit;
  118.       End;
  119.  
  120.   Writeln('Sending Xmodem.....',FileName);
  121.  
  122.   Repeat      { Now we wait for the receiver to tell us whether protocol is}
  123.               { CRC or Checksum.  We'll wait no more than 30 seconds for it}
  124.     MonitorReceive(Ch,30,TimeOut);
  125.               { We want to keep on waiting until we get a timeout, or info }
  126.               { from the receiver.  All garbage is discarded.              }
  127.   Until (TimeOut) or
  128.         (not (TimeOut) and (Ch = #21)) or     { Receiver want's checksum   }
  129.         (not (TimeOut) and (CrcMode) and (Ch='C'));  {Recv'r wants CRC     }
  130.  
  131.   If not Com_Carrier        { If there is no carrier, then exit with error }
  132.     then
  133.       Begin
  134.         XmodemSend := CarrierLostError;
  135.         Close(File1);
  136.         Exit;
  137.       End;
  138.  
  139.   If TimeOut  { If we timed out waiting for receiver, then abort protocol }
  140.     then
  141.       Begin
  142.         Com_TX(CAN);
  143.         Com_TX(CAN);
  144.         XmodemSend := MaxTimeOutError;
  145.         Close(File1);
  146.         Exit;
  147.       End;
  148.  
  149.   CRCMode := (Ch = 'C');   { If receiver sent a 'C', then we're going to }
  150.                            { do a CRC error check, else we'll used Csum. }
  151.  
  152.   NumNAKs := 0;            { No NAK's on blocks yet.  }
  153.   TotalErrors := 0;        { No errors yet.           }
  154.  
  155.   BytesSent := 0;          { Didn't send anything yet }
  156.   TotalBlocks := 0;        { ditto.                   }
  157.  
  158.   GetTime(Hour,Min,Sec,Hund);   { Figure out time to the hundredths of sec.}
  159.   StartTime := (3600 * Hour) + (60 * Min) + Sec + (Hund/100);
  160.  
  161.   GotoXY(1,13);                      { Tell the user what's going on now. }
  162.   Writeln('FileName          : ',FileName);
  163.   Writeln('File Size         : ',FileSize(File1));
  164.   Write('Error Correction  : ');
  165.   If CRCMode
  166.     then
  167.       Writeln('CRC-CCITT')
  168.     else
  169.       Writeln('Checksum');
  170.   Writeln('Block Number      : ',0);
  171.   Writeln('Bytes Sent        : ',0);
  172.   Writeln('Characters/Second : ',0);
  173.   Writeln('Elapsed Time      : ',0,' min. ',0,' sec. ');
  174.   Writeln('Block Errors      : ',0);
  175.   Writeln('Total Errors      : ',0);
  176.  
  177.   BlockNum := 1;                    { First block number is 1             }
  178.   TotalBlocks := 1;                 { We're sending first block now.      }
  179.  
  180.   Repeat
  181.     GetNextBlock(Data,DataLen);     { Get block of data from file         }
  182.     If DataLen > 0                  { If not EOF in file, then.....       }
  183.       then
  184.         Repeat
  185.  
  186.           SendBlock(Data,DataLen,BlockNum,CRCMode);  { ...send the block  }
  187.  
  188.           CancelCode := 0;          { No CAN's received yet on this block }
  189.  
  190.           Repeat
  191.             If KeyPressed              { If user presses a key, error out }
  192.               then
  193.                 Begin
  194.                   XmodemSend := OperatorAbortError;
  195.                   Close(File1);
  196.                   Exit;
  197.                 End;
  198.             If not Com_Carrier         { If carrier drops, error out     }
  199.               then
  200.                 Begin
  201.                   XmodemSend := CarrierLostError;
  202.                   Close(File1);
  203.                   Exit;
  204.                 End;
  205.             MonitorReceive(Ch,10,TimeOut);  {Wait for response from recv'r }
  206.  
  207.             If (not TimeOut) and (Ch = CAN)  { If receiver sends CAN, then }
  208.               then
  209.                 Inc(CancelCode)              { count it to see if we got 2 }
  210.                                              { in a row yet.               }
  211.               else
  212.                 CancelCode := 0;             { Otherwise we couldn't have  }
  213.                                              { gotten 2 in a row.          }
  214.  
  215.             { Keep on looping until we either TimeOut, get an ACK, NAK, or }
  216.             { 2 CAN's in a row.  Discard all garbage.                      }
  217.           Until TimeOut or (Ch=ACK) or (Ch=NAK) or (CancelCode = 2);
  218.  
  219.           Case Ch of           { If we got a NAK, then count up the errors }
  220.             NAK : Begin
  221.                     Inc(NumNAKs);
  222.                     Inc(TotalErrors);
  223.                   End;
  224.             ACK : Begin  { If we got an ACK, then zero error count & go to }
  225.                          { next block.                                     }
  226.                     NumNAKs := 0;
  227.                     BytesSent := BytesSent + DataLen;
  228.                   End;
  229.           End;{Case}
  230.  
  231.           { Now, tell user how the xfer is going. }
  232.  
  233.           GetTime(Hour,Min,Sec,Hund);
  234.           EndTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100);
  235.           If EndTime < StartTime
  236.             then
  237.               EndTime := EndTime + (3600 * 24);
  238.           GotoXY(1,16);
  239.           Writeln('Block Number      : ',TotalBlocks);
  240.           Writeln('Bytes Sent        : ',BytesSent);
  241.           Writeln('Characters/Second : ',Trunc((BytesSent/(EndTime-StartTime)*100))/100:0:2);
  242.           Writeln('Elapsed Time      : ',Trunc(EndTime-StartTime) div 60,' min. ',
  243.                   Trunc(EndTime-StartTime) mod 60,' sec. ');
  244.           Writeln('Block Errors      : ',NumNAKs);
  245.           Writeln('Total Errors      : ',TotalErrors);
  246.  
  247.           { This loop goes until we either get an ACK, or 2 CAN's.  This   }
  248.           { way, we will re-xmit the block if we got a NAK, or a timeout   }
  249.           { as the protocol definition describes.                          }
  250.  
  251.         Until (Ch =ACK) or (CancelCode = 2);
  252.  
  253.     Inc(BlockNum);                  { OK, set up for next block. }
  254.     Inc(TotalBlocks);               { ditto                      }
  255.  
  256.     { This loop repeats for each data block, until DataLen is zero, which  }
  257.     { means the last block xmitted was the last in the file.  The other    }
  258.     { way this loop exits is if we got 2 CAN's in a row from receiver.     }
  259.  
  260.   Until (DataLen=0) or (CancelCode = 2);
  261.  
  262.   If CancelCode = 2   { If we got 2 CAN's, then display the error & exit   }
  263.     then
  264.       Begin
  265.         XmodemSend := RemoteCancelError;
  266.         Close(File1);
  267.         Exit;
  268.       End;
  269.  
  270.   Repeat  { Now that EOF has been reached, we need to send EOT until the  }
  271.           { receiver ACKnowledges it.                                     }
  272.     Com_TX(EOT);
  273.     MonitorReceive(Ch,10,TimeOut);
  274.   Until (TimeOut) or (Ch = ACK);
  275.  
  276.   If TimeOut      { If we timed out while sending the EOT, then issue an  }
  277.                   { error.                                                }
  278.     then
  279.       XmodemSend := MaxTimeOutError;
  280.  
  281.   Close(File1);   { Close the file, and exit }
  282.  
  283. End;
  284.  
  285.  
  286. { This function will receive a file by Xmodem protocol under the name     }
  287. { specified in FileName.  The receiver only needs to know if the user     }
  288. { wants CRC transfers or not.  This routine will handle either 1K or      }
  289. { regular transfers automatically.  It is recommended that if 1K blocks   }
  290. { are used, the CRCMode be turned on.                                     }
  291.  
  292. Function XmodemReceive(    CRCMode : Boolean;
  293.                        Var FileName : String ) : Byte;
  294.  
  295. Var Ch            :Char;        { Character just received                  }
  296.  
  297.     TimeOut,                    { Becomes true when no character has been  }
  298.                                 { received for 10 seconds (a time-out      }
  299.                                 { condition).                              }
  300.  
  301.     CRCOn,                      { True if CRC transfer, False if checksum  }
  302.  
  303.     Done,                       { True if protocol is to exit, either on   }
  304.                                 { cancel or EOT condition.                 }
  305.  
  306.     StartUp,                    { Used to enter the main loop and not wait }
  307.                                 { for a character from modem once CRC has  }
  308.                                 { been initiated.  Is False if Checksum.   }
  309.  
  310.     ForceACK      : Boolean;    { Becomes true if block received = Last    }
  311.                                 { block sent.  If True, block is ACK'd,    }
  312.                                 { but not saved to disk.                   }
  313.  
  314.     CRC,                        { Holds current CRC value as data comes in }
  315.  
  316.     Code,                       { Used to return an error code from IBMCom }
  317.                                 { when we initialize the COM Port.         }
  318.  
  319.     BlockCRC,                   { Holds the CRC of the block, as sent by   }
  320.                                 { transmitter. This is compared with the   }
  321.                                 { CRC variable to determine if block is    }
  322.                                 { error-free.                              }
  323.  
  324.     Status,                     { Status code for writing to disk.         }
  325.  
  326.     Hour,                       { Holds hour number (0-23) of timing code  }
  327.     Min,                        { Holds minute number (0-59) in timing     }
  328.     Sec,                        { Holds second number (0-59) in timing     }
  329.     Hund          : Word;       { Holds 100th of a second in timing code   }
  330.  
  331.     Count,                      { Used in counting C's sent in attempt to  }
  332.                                 { initiate CRC mode.                       }
  333.  
  334.     Operation,                  { The process of receiving an Xmodem block }
  335.                                 { has been broken down into six different  }
  336.                                 { operations.  These are shown in the large}
  337.                                 { Case statement in the main loop.  This   }
  338.                                 { variable is used to keep the steps in    }
  339.                                 { sequence.                                }
  340.  
  341.     NumNAKS,                    { Counts number of NAK's that have been    }
  342.                                 { sent on this block.  If it ever exceeds  }
  343.                                 { 10, then two CAN's are sent & protocol   }
  344.                                 { is aborted.                              }
  345.  
  346.     DataNum,                    { Holds how many data bytes in the block   }
  347.                                 { have been received. This increments by   }
  348.                                 { one as each byte comes in, and stops     }
  349.                                 { when it equals MaxDataLength.            }
  350.  
  351.     MaxDataLength : Integer;    { If first character of block is SOH, then }
  352.                                 { this is 128.  If first char is STX, then }
  353.                                 { this is 1024, for 1K Xmodem support.     }
  354.  
  355.     CANNum,                     { Used to count how many CAN chars have    }
  356.                                 { been received in a row.  Once two have   }
  357.                                 { been received in a row, protocol exits   }
  358.                                 { under a cancel condition.                }
  359.  
  360.     EOTNum,                     { Used to count how many EOT chars have    }
  361.                                 { been received in a row.  Once two have   }
  362.                                 { been received in a row, protocol exits   }
  363.                                 { because transmission is complete.        }
  364.  
  365.     Checksum,                   { Used to hold the checksum of the data in }
  366.                                 { checksum transfers.                      }
  367.  
  368.     CRCBytes,                   { The CRC is two bytes long.  In CRC mode, }
  369.                                 { this is a counter to make sure both bytes}
  370.                                 { of the CRC have been received from the   }
  371.                                 { transmitter.                             }
  372.  
  373.     BlockNum,                   { Holds the current block number as sent   }
  374.                                 { by transmitter.  Starts at 1, and        }
  375.                                 { increments to 255, then to 0, and starts }
  376.                                 { counting up over again.                  }
  377.  
  378.     TotalErrors,                { Counter used to hold the total number of }
  379.                                 { errors encountered thus far during the   }
  380.                                 { transfer.  Used for screen information   }
  381.                                 { only.                                    }
  382.  
  383.     LastBlock     : Byte;       { Holds the last block's number.  If this  }
  384.                                 { ever equals the value of BlockNum, then  }
  385.                                 { ForceACK is set to true.                 }
  386.  
  387.     TotalBlocks,                { Similar to BlockNum, except to does not  }
  388.                                 { wrap around to zero when it reaches 255  }
  389.                                 { Used to display info for the user only.  }
  390.  
  391.     BytesReceived : LongInt;    { Counts the total number of bytes received}
  392.                                 { thus far in the transfer.  Used to calc. }
  393.                                 { the CPS field on the screen.             }
  394.  
  395.     StartTime,                  { Holds the time, in seconds, when the     }
  396.                                 { protocol was started.                    }
  397.  
  398.     EndTime       : Real;       { Holds the current time, in seconds.  Used}
  399.                                 { with StartTime to determine how much time}
  400.                                 { has passed since start of transfer, and  }
  401.                                 { to figure CPS rate.                      }
  402.  
  403.     Data          : BlockType;  { Buffer used to hold actual               }
  404.                                 { file data as it is received.             }
  405.                                 { Later, it is written to disk             }
  406.  
  407.     File1         : File;       { File handle for disk I/O                 }
  408.  
  409.  
  410.     { This procedure is used only for displaying information to the user   }
  411.     { about how the xfer is going.  There are a couple of places where     }
  412.     { this is needed, so it has been put into a procedure.                 }
  413.  
  414.     Procedure UpdateVideo;
  415.     Begin
  416.       GetTime(Hour,Min,Sec,Hund);   { Get the current time in seconds here }
  417.       EndTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100);
  418.  
  419.       If EndTime < StartTime      { Allow for xfers spanning over midnight }
  420.         then
  421.           EndTime := EndTime + (3600 * 24);
  422.  
  423.       GotoXY(1,15);
  424.       Writeln('Block Number      : ',TotalBlocks);
  425.       Writeln('Bytes Received    : ',BytesReceived);
  426.       Writeln('Characters/Second : ',Trunc((BytesReceived/(EndTime-StartTime)*100))/100:0:2);
  427.       Writeln('Elapsed Time      : ',Trunc(EndTime-StartTime) div 60,' min. ',
  428.               Trunc(EndTime-StartTime) mod 60,' sec. ');
  429.       Writeln('Block Errors      : ',NumNAKs);
  430.       Writeln('Total Errors      : ',TotalErrors);
  431.     End;
  432.  
  433.  
  434.     { There are many places in the receive routine where it is necessary to}
  435.     { send a NAK character.  This procedure sends the NAK, and updates the }
  436.     { NAK counter so we know how many NAK's have been sent on this block.  }
  437.     { If the NAK counter exceeds 10, we immediately send two CAN's and exit}
  438.     { the protocol due to exceeding the timeout maximum (when Operation is }
  439.     { 200).                                                                }
  440.  
  441.     Procedure SendNak;
  442.     Begin
  443.       Inc(NumNAKs);                         { Increment the counters by 1 }
  444.       Inc(TotalErrors);
  445.       If NumNAKs < 11       { If we didn't exceed the limit, send the NAK }
  446.         then
  447.           Com_TX(NAK);
  448.       Operation := 1;       { Next operation is wait for next SOH char    }
  449.       If NumNAKs = 11       { unless we met NAK limit, in which case we   }
  450.         then                { must exit the protocol after sending 2 CAN's}
  451.           Begin
  452.             Operation := 200;
  453.             XmodemReceive := MaxTimeOutError;
  454.           End;
  455.       GotoXY(1,19);         { Tell user there was an error }
  456.       Writeln('Block Errors      : ',NumNAKs);
  457.       Writeln('Total Errors      : ',TotalErrors);
  458.     End;
  459.  
  460.  
  461.  
  462.  
  463. Begin
  464.  
  465.   If not Com_Carrier        { If there is no carrier, then exit because we }
  466.     then                    { can't send anything like that.               }
  467.       Begin
  468.         XmodemReceive := CarrierLostError;
  469.         Exit;
  470.       End;
  471.  
  472.   XmodemReceive := NoError;               { OK, assume there are no errors }
  473.  
  474.   If not OpenFile(File1,Wt,1,FileName)    { Try to open the file here      }
  475.     then
  476.       Begin
  477.         XmodemReceive := FileError;       { If not successful, then exit   }
  478.         Exit;                             { under error condition.         }
  479.       End;
  480.  
  481.   If CRCMode                              { If we were told to support CRC }
  482.     then                                  { then tell user we're trying to.}
  483.       Writeln('Testing for support of CRC...');
  484.  
  485.   CRCOn         := True;    { Assume we are really doing CRC }
  486.  
  487.   Count         := 0;       { This counts 3 second timeouts for us.  Start }
  488.                             { with none while attempting to initiate CRC   }
  489.  
  490.   CANNum        := 0;       { No CAN's received yet.                       }
  491.  
  492.   EOTNum        := 0;       { No EOT's received yet.                       }
  493.  
  494.   ForceACK      := False;   { Don't for an ACK on any blocks yet.          }
  495.  
  496.   LastBlock     := 0;       { Didn't have a last block, so set it to zero  }
  497.  
  498.   BytesReceived := 0;       { No bytes received yet.                       }
  499.  
  500.   BlockNum      := 0;       { No blocks received yet, so set to zero.      }
  501.  
  502.   TotalBlocks   := 0;       { Ditto.                                       }
  503.  
  504.   TotalErrors   := 0;       { No errors yet either.                        }
  505.  
  506.   TimeOut := True;          { Prime the Repeat/Until loop }
  507.  
  508.   If CRCMode           { If we're supposed to do CRC, then try initiation. }
  509.     then
  510.        Repeat          { Start of loop }
  511.  
  512.          If TimeOut    { If we had a timeout or this is start of loop, then}
  513.            then
  514.              Com_TX('C');        { send a 'C' to try to initiate CRC xfer. }
  515.  
  516.          If not Com_Carrier  { If we don't have a carrier for some reason, }
  517.            then
  518.              Begin                              { Return an error to user. }
  519.                XmodemReceive := CarrierLostError;
  520.                Close(File1);
  521.                Exit;
  522.              End;
  523.  
  524.          MonitorReceive(Ch,3,TimeOut);        { Monitor receive for 3 secs }
  525.  
  526.          If TimeOut  { If no response in 3 secs, then increment timeout ctr}
  527.            then
  528.              Inc(Count);
  529.  
  530.          { This loop continues until we've either got an SOH (start of a   }
  531.          { 128 byte block), a STX (start of 1K block), or the timeout ctr  }
  532.          { is 3 (meaning that the transmitter does not support CRC, and the}
  533.          { xfer is to be checksum).                                        }
  534.  
  535.        Until (Count = 3) or (Ch = SOH) or (Ch = STX);
  536.  
  537.  
  538.   If TimeOut   { If we're still sitting on a timeout, then assume a  }
  539.     then       { checksum.  If TimeOut = False, then Count <> 3, and }
  540.                { CRC mode was initiated from above loop.             }
  541.       Begin
  542.         Writeln('Resorting to checksum now...');
  543.         CRCOn   := False;     { Not doing CRC here                    }
  544.         StartUp := False;     { set the start up flag for checksum    }
  545.         Com_TX(NAK);          { send te first NAK to initate checksum }
  546.       End
  547.     else
  548.       Begin
  549.         StartUp := True;      { If CRC is initiated, then set startup flag }
  550.       End;
  551.  
  552.   NumNAKs := 0;                                 { No NAK's for errors yet. }
  553.   Done := False;        { Prime the loop, we're not done - we're beginning }
  554.   Operation := 1;                  { First operation we're looking at is 1 }
  555.  
  556.   GetTime(Hour,Min,Sec,Hund);   { Grab the starting time before we get     }
  557.   StartTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100);  { started     }
  558.  
  559.   GotoXY(1,13);                           { Tell the user what's going on. }
  560.   Writeln('FileName          : ',FileName);
  561.   Write('Error Correction  : ');
  562.   If CRCOn
  563.     then
  564.       Writeln('CRC-CCITT')
  565.     else
  566.       Writeln('Checksum');
  567.   Writeln('Block Number      : ',0);
  568.   Writeln('Bytes Received    : ',0);
  569.   Writeln('Characters/Second : ',0);
  570.   Writeln('Elapsed Time      : ',0,' min. ',0,' sec. ');
  571.   Writeln('Block Errors      : ',0);
  572.   Writeln('Total Errors      : ',0);
  573.  
  574.  
  575.   { This loop has a very large CASE statement in it used to determine how  }
  576.   { data received in interpreted.  I have divided the receiving of the     }
  577.   { Xmodem block into 5 steps (the variable Operation iterates from 1 to 5 }
  578.   { on every block, and it's value determines which step is next taken in  }
  579.   { the loop.)  The following is a list of the steps I have defined:       }
  580.  
  581.   { Step 1:  Receive an SOH, STX, EOT, or CAN character.  SOH means that   }
  582.   {          a 128 byte block is comming.  STX means that a 1024 byte      }
  583.   {          block is comming.  EOT means that the file xfer is done. The  }
  584.   {          code requires two of these to recognize a true EOT condition. }
  585.   {          A CAN character means the remote end has aborted the xfer.    }
  586.   {          The code must receive two of these to recognize a true cancel }
  587.   {          condition.                                                    }
  588.  
  589.   { Step 2:  Receive the block number.  This is just 1 byte long.          }
  590.  
  591.   { Step 3:  Receive the one's complement of the block number. If the char }
  592.   {          received here is NOT the one's complement of the block number }
  593.   {          then the code simply returns to step 1, assuming the chars    }
  594.   {          received thus far were garbage caused by line noise.  If the  }
  595.   {          byte here is the correct one's complement of the block, then  }
  596.   {          it is assumed that the block has begun.  If the block # does  }
  597.   {          equal the next block expected in sequence, protocol aborts    }
  598.   {          quickly with 2 CAN's.  If the block number is the same as the }
  599.   {          last one, then a flag is set to force an ACK to be sent for   }
  600.   {          this block down in step 5.                                    }
  601.  
  602.   { Step 4:  This step involves receiving the actual data of the block.    }
  603.   {          If an SOH was received in step 1, then 128 bytes are expected }
  604.   {          from the transmitter.  If an STX was received in step 1, then }
  605.   {          1024 bytes are expected from transmitter.  As each byte of    }
  606.   {          data comes in, the CRC or Checksum (whichever is aplicable    }
  607.   {          for this xfer) is updated.  The data is stored in an array.   }
  608.   {          When the number of bytes expected in the data has been rec'd  }
  609.   {          we proceed to step 5.                                         }
  610.  
  611.   { Step 5:  Receive the block check.  For CRC xfers, a 2 byte CRC is sent }
  612.   {          on the end of the block.  For Checksum xfers, sum of the      }
  613.   {          data bytes in the block mod 256 is sent.  This step invloves  }
  614.   {          receiving the CRC or checksum, and then comparing this value  }
  615.   {          with the one we figured in step 4 as the data came in.  If    }
  616.   {          the check values are the same, or the ForceACK flag (see step }
  617.   {          3) is True, then we send an ACK to the transmitter.  If the   }
  618.   {          ForceACK flag is false, we save the data to the disk file,    }
  619.   {          otherwise, the data in this block is already in the file, and }
  620.   {          does not need to be saved twice in a row.  If the check val   }
  621.   {          does NOT equal the one we figured, then a NAK is sent, and    }
  622.   {          the data is not saved to disk. In either case, we go to step  }
  623.   {          1 & continue processing.  If the block is ACK'd, then we zero }
  624.   {          our NAK counter because 10 errors must happen on the same     }
  625.   {          block in order to cause an error.  If the block comes thru OK }
  626.   {           then we needn;t keep track of the NAKs for it anymore.       }
  627.  
  628.   { Keep in mind that if at any time a span of 10 seconds occurs without   }
  629.   { getting a character from the xmitter, a timeout condition occurrs.     }
  630.   { When we have a timeout condition, we send a NAK, and increment our     }
  631.   { NAK counter.  If the NAK counter ever exceeds 10, the protocol is      }
  632.   { aborted by sending two CAN's to the transmitter, and exiting the prog. }
  633.   { Also, if ever we lose carrier, or the user presses a key, then the     }
  634.   { protocol is aborted immediately with 2 CAN's.                 -Andrew  }
  635.  
  636.  
  637.   Repeat                     {Start a loop that only ends when Done = True }
  638.  
  639.     If not Com_Carrier    { If we ever lose carrier, generate error & exit }
  640.       then
  641.         Begin
  642.           XmodemReceive := CarrierLostError;
  643.           Close(File1);
  644.           Exit;
  645.         End;
  646.           { StartUp is True if we've initiated CRC correction and the first }
  647.           { SOH or STX character is held in variabel Ch.                    }
  648.     If StartUp                         { So, if first SOH/STX is in Ch......}
  649.       then
  650.         Begin
  651.           StartUp := False;     {...then reset flag, and reset NAK Count... }
  652.           NumNAKs := 0;
  653.         End
  654.       else                      { ....otherwise try waiting for a character }
  655.         Begin
  656.  
  657.            { When Operation = 200, the protocol has an abort condition, and }
  658.            { We should not wait around for any characters.                  }
  659.  
  660.           If Operation <> 200     { If we've not got an abort condition.... }
  661.             then
  662.               Begin
  663.                 Repeat    {...then start a loop that continues until we've  }
  664.                           { received a character or 10 timeouts have        }
  665.                           { occurred on this block.                         }
  666.  
  667.                   If Com_RX_Empty  { If the receive buffer is empty (i.e.   }
  668.                                    { there is no character waiting to be    }
  669.                                    { used), then start waiting......        }
  670.                     then
  671.                       Begin
  672.                         MonitorReceive(Ch,10,TimeOut);  { For for 10 secs   }
  673.  
  674.                         If TimeOut and (EOTNum = 1) { If 10 secs pass w/o   }
  675.                             { character & we've been waiting for the second }
  676.                             { EOT, then we'll issue a wairning to user and  }
  677.                             { exit, assuming the 2ns EOT is not comming.    }
  678.                           then
  679.                             Begin
  680.                               XmodemReceive := EOTTimeOut;
  681.                               Close(File1);
  682.                               Exit;
  683.                             End;
  684.                         If TimeOut   { If we had a timeout, but were not    }
  685.                           { necessarily looking for a 2nd EOT, then we'll   }
  686.                           { increment out timeout counter & send a NAK.     }
  687.                           then
  688.                             SendNAK;
  689.                       End
  690.                     else  { If there was a character in the buffer, by all  }
  691.                           { means, let's go get it now and skip all that    }
  692.                           { junk in the above Begin/End pair.               }
  693.                       Begin
  694.                         Ch := Com_RX;
  695.                         TimeOut := False;
  696.                       End;
  697.  
  698.                   If KeyPressed   { If the user presses a key, let's cancel }
  699.                     then                                { the protocol now. }
  700.                       Begin
  701.                         Operation := 200;
  702.                         XmodemReceive := OperatorAbortError;
  703.                         TimeOut := False;
  704.                       End;
  705.  
  706.                 Until (Not TimeOut) or (NumNAKs = 11);    { This loop keeps }
  707.                   { on going until we get a character or 10 timeouts have   }
  708.                   { happened.                                               }
  709.  
  710.               End;
  711.         End;
  712.  
  713.      { OK, here is where the code decides what actually gets done with each }
  714.      { character it receives.  Operation holds the step number it will do   }
  715.      { next.  As each step is completed, Operation is set to the next       }
  716.      { consecutive step, so it will do the right thing when it loops thru   }
  717.      { here next time.                                                      }
  718.  
  719.  
  720.     Case Operation of
  721.       1 : Begin                 { Step 1, waiting for SOH, STX, EOT, or CAN }
  722.             Case Ch of
  723.               SOH,                           { If it was SOH or STX, then...}
  724.               STX : Begin
  725.                       Operation  := 2;        { we think block has started. }
  726.                       CANNum     := 0;      { reset a few counters & set up }
  727.                       EOTNum     := 0;                          { defaults. }
  728.                       ForceACK   := False;
  729.                       NumNAKs    := 0;
  730.                       Case Ch of
  731.                         SOH : MaxDataLength := 128;  { SOH = 128 byte block }
  732.                         STX : MaxDataLength := 1024; { STX = 1024 byte blk. }
  733.                       End;{Case}
  734.                     End;
  735.  
  736.               EOT : Begin              { If it was an EOT, then...          }
  737.                       CANNum := 0;     { we certainly didn't get 2 of these.}
  738.                       Inc(EOTNum);     { but increment EOT counter.         }
  739.                       If EOTNum = 2      { If we got 2 EOT's together, then }
  740.                         then
  741.                           Begin
  742.                             Com_TX(ACK);      { ...ACK the 2nd EOT and exit }
  743.                                               { under a normal condition.   }
  744.                             Writeln('End of Transmission.');
  745.                             XmodemReceive := NoError;
  746.                             Done := True;
  747.                           End
  748.                         else      { But if this was first EOT, we'll NAK it }
  749.                           Begin
  750.                             Com_TX(NAK);
  751.                           End;
  752.                     End;
  753.               CAN : Begin  { If char was a CAN, then }
  754.                       Inc(CANNum);                  { Increment CAN counter }
  755.                       If CANNum = 2    { If we've gotten 2, cancel w/ error }
  756.                         then
  757.                           Begin
  758.                             Done := True;
  759.                             Writeln('Xmodem Receive Cancelled.');
  760.                             XmodemReceive := RemoteCancelError;
  761.                           End;
  762.                         { If we've only gotten 1 CAN, ignore it right yet. }
  763.                     End;
  764.  
  765.               else  Begin     { If char wasn't SOH, STX, EOT, or CAN, then }
  766.                       EOTNum := 0;  { Ignore it, as it may be garbage, but }
  767.                       CANNum := 0;  { we'll reset our counters anyway.     }
  768.                       ForceACK := False;
  769.                     End;
  770.             End;{Case}
  771.           End;
  772.       2 : Begin  { Step 2 - Get the block number }
  773.             BlockNum := Ord(Ch);     { We're assuming from the fact that an }
  774.             Operation := 3;   { SOH or STX was received that the block # is }
  775.               { next to be sent.  But if garbage caused the SOH/STX, we'll  }
  776.               { catch the error in the next step.                           }
  777.           End;
  778.       3 : Begin  { Step 3 - Get ones complement of block number }
  779.  
  780.             If BlockNum = (not Ord(Ch))   { If one's compl is correct for  }
  781.                         { the block # received in step 2, we're officially }
  782.                         { assuming the block has started.                  }
  783.               then
  784.                 Begin
  785.                   If (( (LastBlock + 1) and $FF ) = BlockNum) or
  786.                      (LastBlock = BlockNum)
  787.                { If the block number is the next in sequence, or it is the }
  788.                { same one we got before, we'll accept it.                  }
  789.                     then
  790.                       Begin
  791.                         ForceACK := LastBlock = BlockNum;  { If last block }
  792.                                { equals this block, then ForceACK is True. }
  793.                         Operation := 4;  { Go to next step on next loop    }
  794.                         DataNum  := 0;   { No data received in block yet.  }
  795.                         CRC      := 0;   { Clear out the check, regardless }
  796.                         Checksum := 0;   { if it's CRC or checksum         }
  797.                       End
  798.                     else
  799.                       Begin          { If block # is our of seq, STOP NOW! }
  800.                         Operation := 200;
  801.                         XmodemReceive := BlockOutOfSequenceError;
  802.                       End;
  803.                 End
  804.               else
  805.                 Begin  { If line noise caused a false SOH/STX, then the   }
  806.                        { block # and the block #'s one's compl won't be   }
  807.                        { correct, so if that happens, here is where we're }
  808.                        { going back to step 1 to wait for a better SOH or }
  809.                        { STX.                                             }
  810.                   Operation := 1;
  811.                 End;
  812.           End;
  813.       4 : Begin  { Step 4 - Receive the data block. }
  814.  
  815.             Inc(DataNum);            { Increment our data received counter }
  816.             Data[DataNum] := Ord(Ch);      { store character in the array. }
  817.  
  818.             If CRCOn
  819.               then
  820.                 CRC := UpdCRC(Ord(Ch),CRC)  { If CRC mode, then update CRC }
  821.               else
  822.                 Checksum := Checksum + Ord(Ch);    { else update checksum. }
  823.  
  824.             If (DataNum = MaxDataLength) { If we've received the total amt }
  825.                      { of the data (determined in step 1 as either 128 or  }
  826.                      { 1024), then it's time to go to step 5.              }
  827.               then
  828.                 Begin
  829.                   Operation := 5;
  830.                   CRCBytes := 0;     { have gotten 0 bytes of CRC yet (see }
  831.                                      { step 5 about this).                 }
  832.  
  833.                   ReverseWord(CRC);  { We're reversing our CRC to be right }
  834.   { for transmission.  The xmitter will be sending it bit reversed.        }
  835.   { NOTE: From this point on, we don't  actually have the CRC of the data  }
  836.   { in the CRC variable.  We only have what we expect the xmitter to send. }
  837.  
  838.                 End;
  839.           End;
  840.       5 : Begin  { Step 5 - get error check & verify block's correctness }
  841.  
  842.             If CRCOn  { If we're doing CRC, then handle 2 CRC bytes}
  843.               then
  844.                 Begin
  845.                   If CRCBytes = 0  { If we've not gotten anything for CRC  }
  846.                                    { yet, then treat this char as the high }
  847.                                    { byte of the 16 bit word.              }
  848.                     then
  849.                       Begin
  850.                         BlockCRC := Ord(Ch) * 256;     { Make it high byte }
  851.                         Inc(CRCBytes);    { increment byte counter for CRC }
  852.                       End
  853.                     else
  854.                       Begin  { If we've gotten a byte before on the CRC,   }
  855.                              { this one is the lower byte.                 }
  856.  
  857.                         BlockCRC := BlockCRC + Ord(Ch);  { add in low byte }
  858.  
  859.                         If (CRC = BlockCRC) or ForceACK  { If CRC is right }
  860.                            { or we're forced to ACK this block, then we'll }
  861.                            { send an ACK.......                            }
  862.  
  863.                           then
  864.                             Begin
  865.                               Com_TX(ACK);          { Send ACK to xmitter. }
  866.                               LastBlock := BlockNum; { Keep track of the   }
  867.                                 { last block sent in LastBlock             }
  868.  
  869.                               Operation := 1;  { Back to waiting for SOH or}
  870.                                                { STX (next block starting) }
  871.  
  872.                               If not ForceACK  { If we were not forced to  }
  873.                                 { ACK this block, then we can save it to   }
  874.                                 { disk.                                    }
  875.  
  876.                                 then
  877.                                   Begin
  878.                                       { write MaxDataLength (step 4) bytes }
  879.                                       { to disk }
  880.                                     BlockWrite(File1,Data,MaxDataLength,Status);
  881.                                       { Increment counter of bytes         }
  882.                                     BytesReceived := BytesReceived + MaxDataLength;
  883.                                       { Increment counter of blocks        }
  884.                                     Inc(TotalBlocks);
  885.                                   End;
  886.                               NumNAKs := 0;  { Zero NAK/error counter      }
  887.                               UpdateVideo;   { Tell user what's going on.  }
  888.                             End
  889.                           else
  890.                             { If the CRC ** DOES NOT ** match, and we're   }
  891.                             { not forcing an ACK on this block, then we'll }
  892.                             { have to NAK it because it's not good data.   }
  893.                             Begin
  894.  
  895.                               Repeat  { Wait until there's not anything in }
  896.                               { the buffer.  This is important to do if    }
  897.                               { there was an error.  Line noise might have }
  898.                               { caused a bunch of extra characters to come }
  899.                               { across, and we'll want to get rid of them. }
  900.                                 Ch := Com_Rx;
  901.                               Until Com_RX_Empty;
  902.  
  903.                               SendNAK;  { NAK the block, Operation is      }
  904.                               { automatically set to 1, unless we've gone  }
  905.                               { more than 10 errors on this block.         }
  906.                             End;
  907.                       End;
  908.                 End
  909.  
  910.   { Whew!  That was a lot of stuff to do when we get to the end of the    }
  911.   { block.  Fortunately, most of it (updating video, saving to disk) all  }
  912.   { happens after we've put the ACK in the buffer to be sent.  Thus, the  }
  913.   { ACK is being xmitted, and the next block may already be being sent to }
  914.   { the COM port buffer before we actually get out of here & ready to     }
  915.   { take the next block.  The other steps are very quick by comparison,   }
  916.   { so we easily catch up with the buffer.  On faster machines, we may    }
  917.   { find the code actually waiting for the next character to come in only }
  918.   { a few loops later.  Of course, baud rate has a lot to do with how     }
  919.   { much waiting we're going to do.                                       }
  920.  
  921.   { Enough of the excursions...now let's see what happens when we check   }
  922.   { a block & save it under Checksum correction:                          }
  923.  
  924.               else
  925.                 Begin
  926.  
  927.                   If (Checksum = Ord(Ch)) or ForceACK  { If the csum is    }
  928.                       { correct or we're forced to ACK this block, then    }
  929.                     then
  930.                       Begin
  931.                         Com_TX(ACK);                       { Send the ACK  }
  932.                         LastBlock := BlockNum;  { Keep track of blk. seq.  }
  933.                         Operation := 1;     { we're going to step 1 again  }
  934.  
  935.                         If not ForceACK  { If we're not forced to ACK this }
  936.                             { block, then we'll save it to disk.           }
  937.                           then
  938.                             Begin
  939.                                 { Write the data block }
  940.                               BlockWrite(File1,Data,MaxDataLength,Status);
  941.                                 { increment bytes recevied counter }
  942.                               BytesReceived := BytesReceived + MaxDataLength;
  943.                                 { increment blocks counter }
  944.                               Inc(TotalBlocks);
  945.                             End;
  946.                         NumNAKs := 0;  { Zero out error counter            }
  947.                         UpdateVideo;   { Tell user what has been happening }
  948.                       End
  949.                     else
  950.                       Begin  { If we're NAKing this block, then....}
  951.  
  952.                         Repeat         { wait for the buffer to get empty. }
  953.                           Ch := Com_Rx;
  954.                         Until Com_RX_Empty;
  955.                         SendNAK;                 { Send a NAK on the block }
  956.                       End;
  957.                 End;
  958.           End;
  959.  
  960.       200 : Begin  { Here is where it winds up if Operation = 200 }
  961.         { This isn't actually a step.  But the computer winds up here if   }
  962.         { ever we need to abort the protocol, and need to tell the xmitter }
  963.         { this by sending two CAN's.                                       }
  964.               Com_TX(CAN);
  965.               Com_TX(CAN);
  966.               Done := True;  { time to drop out of loop }
  967.             End;
  968.     End;{Case}
  969.  
  970.   Until Done;    { Loop only lasts until Done }
  971.  
  972.   Close(File1);  { Close the file now }
  973.  
  974. End;
  975.  
  976.  
  977.